庫是寫好的現有的,成熟的,可以復用的二進制檔案。現實中每個專案都要依賴很多基礎的底層庫(iostream, vector, sys/resource.h ...),不可能每個人的程式都從零開始。
讀到以上說明的時候,是不是有一種 りしれ供さ小 的感覺?因此今天我帶來了庫的實際應用範例!
之前在某一間公司實習的時候,有天需要使用工業電腦控制通訊協議為CAN BUS的機器,設備商有提供一個USB轉CAN總線轉換器(I7565H1/H2),上網查了一下,手冊中的程式庫都有在網路上公開,內容也滿詳細的。
但是實際下載後發現,專案中並沒有提供SDK完整的原始碼,而是全部都包裝成動態與靜態庫供客戶選用。
不過想一想這也正常,畢竟這些都是智慧財產,總不可能把硬體data sheet和軟體SDK原始碼全部公開,萬一被抄襲了怎麼辦?
也有可能是怕使用者胡亂修改程式破壞硬體,算是某種程度的防呆機制吧,提供API給使用者,只能按照手冊上的規範操作。
以下是I7565H1/H2 SDK的檔案架構
├── 99-I7565H2.rules
├── bootinstall.service
├── ChangeLog
├── doc
│ └── linux_I7565H1H2_Software_Manual.pdf
├── examples
│ ├── i7565H1H2_a
│ ├── i7565H1H2.c
│ ├── i7565H1H2_so
│ └── Makefile
├── I7565H1H2_hotplug
├── I7565H1H2_install
├── include
│ ├── codes.h
│ ├── common.h
│ ├── debug.h
│ ├── global.h
│ ├── i7000.h
│ ├── i7565H1H2.h
│ ├── i7k.h
│ ├── Makefile
│ ├── msw.h
│ ├── sio.h
│ ├── threadfun.h
│ └── timer.h
├── lib
│ ├── libI7565H1H2_64.a
│ ├── libI7565H1H2_64.so -> ../lib/libI7565H1H2_64.so.1
│ ├── libI7565H1H2_64.so.1 -> ../lib/libI7565H1H2_64.so.1.0
│ ├── libI7565H1H2_64.so.1.0
│ ├── libI7565H1H2.a
│ ├── libI7565H1H2_arm.a
│ ├── libI7565H1H2_arm.so -> ../lib/libI7565H1H2_arm.so.1
│ ├── libI7565H1H2_arm.so.1 -> ../lib/libI7565H1H2_arm.so.1.0
│ ├── libI7565H1H2_arm.so.1.0
│ ├── libI7565H1H2.so -> ../lib/libI7565H1H2.so.1
│ ├── libI7565H1H2.so.1 -> ../lib/libI7565H1H2.so.1.0
│ ├── libI7565H1H2.so.1.0
│ ├── libi7k_64.a
│ ├── libi7k_64.so -> ../lib//libi7k_64.so.1
│ ├── libi7k_64.so.1 -> ../lib//libi7k_64.so.1.0
│ ├── libi7k_64.so.1.0
│ ├── libi7k.a
│ ├── libi7k_arm.a
│ ├── libi7k_arm.so -> libi7k_arm.so.1
│ ├── libi7k_arm.so.1 -> libi7k_arm.so.1.0
│ ├── libi7k_arm.so.1.0
│ ├── libi7k.so -> ../lib//libi7k.so.1
│ ├── libi7k.so.1 -> ../lib//libi7k.so.1.0
│ └── libi7k.so.1.0
├── Makefile
└── README
可以看到整個軟體SDK中只有examples/i7565H1H2.c是可供修改的範例原始碼(內容是API的使用範例),其他都是讓客戶使用的靜態庫、動態庫與標頭檔。
因此接下來範例內容,就是使用上一篇編譯出來的靜態與動態函式庫還有標頭檔(用來模擬別人的庫),在沒有庫原始碼的情況下,如何與主程式鍊結成最終的執行檔(使用別人的庫),而不是向昨天一樣,從原始碼編譯成庫之後再與主程式鍊結。
定義:鍊結器尋找鍊結文件的路徑。
link_directories( [directory1] [directory2] ...)
> [directory] : 庫路徑
$ git clone https://github.com/m11112089/2023_iT_CMake.git
$ cd ~/2023_iT_CMake/Day11
$ cp ~/2023_iT_CMake/Day10/build/lib/* ~/2023_iT_CMake/Day11/lib/
// 那個星號是代表該目錄下所有的檔案
kai@esoc:~/2023_iT_CMake/Day11/lib$ cp ~/2023_iT_CMake/Day10/build/lib/* ~/2023_iT_CMake/Day11/lib/
kai@esoc:~/2023_iT_CMake/Day11/lib$ tree
.
├── libmysqrt_a.a
└── libmysqrt_so.so
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 將庫所在路徑加入鍊結器的搜尋路徑中
add_executable(main_a src/main.cpp)
# 將main.cpp編譯成一個可執行文件main_a
target_link_libraries(main_a mysqrt_a)
# 將mysqrt_a鍊結到main_a
add_executable(main_so src/main.cpp)
# 將main.cpp編譯成一個可執行文件main_so
target_link_libraries(main_so mysqrt_so)
# 將mysqrt_so鍊結到main_so
$ cd build
$ cmake ..
$ make
$ ./main_a 10
$ ./main_so 10
由此可見,只要路徑正確程式可以在沒有庫原始碼的情況順利編譯並執行完畢。
注意一定要有標頭檔,原因在於C++的編譯方式為個別編譯(separate compilation),能讓程式分為數個檔案,並且每一個都能獨立編譯成binary檔,而能夠獨立的編譯的關鍵是要有標頭檔的"宣告",在最後練接(linking)的階段才會去尋找"宣告"如何實現的"定義"。